﻿using System;
using System.Collections.Generic;
using System.Common.Error;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Threading;

namespace System.Common
{
  [DebuggerStepThrough]
  [DebuggerNonUserCode]
  public class WeakEventBase : IDisposable
  {
    protected readonly List<WeakDelegate> Eventhandlers = new List<WeakDelegate>();

    ~WeakEventBase()
    {
      Dispose();
    }

    protected internal static bool CanDispatcher( WeakDelegate wd )
    {
      try
      {
        if ( wd == null )
          return false;

        if ( WeakDelegate.Dispatcher == null )
          return false;

        if ( WeakDelegate.Dispatcher.Thread == null )
          return false;

        if ( WeakDelegate.Dispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId )
          return false;
      }
      catch ( Exception ex )
      {
        ExceptionManager.LogException( ex );
        return false;
      }

      return true;
    }

    protected internal void InternalInvoke( WeakDelegate wd, Action action )
    {
      if ( action == null )
        return;

      if ( CanDispatcher( wd ) )
      {
        var th = new Thread( callerStack =>
                             {
                               try
                               {
                                 WeakDelegate.Dispatcher.Invoke( action, DispatcherPriority.Send );
                               }
                               catch ( Exception ex )
                               {
                                 ProcessException( ex, wd, callerStack );
                                 throw;
                               }
                             } );

        var stack = new StackTrace( true );
        th.SetApartmentState( ApartmentState.STA );
        th.Start( stack );
      }
      else
      {
        try
        {
          action();
        }
        catch ( Exception ex )
        {
          ProcessException( ex, wd );
          throw;
        }
      }
    }

    private void ProcessException( Exception ex, WeakDelegate wd, object callerStack = null )
    {
      try
      {
        Trace.WriteLine( "Exception in WeakEventBase.InternalInvoke " + ex.Message );
        if ( wd != null )
        {
          var method = wd.Method;
          if ( method == null )
            Trace.WriteLine( "WeakDelegate.Method property is null" );
          else
          {
            var type = wd.Method.ReflectedType;
            if ( type == null )
            {
              Trace.WriteLine( "WeakDelegate.Method.ReflectedType property is null" );
              Trace.WriteLine( "Invoke " + wd.Method );
            }
            else
              Trace.WriteLine( "Invoke " + type.FullName + "/" + wd.Method );
          }
        }

        Trace.WriteLine( "WeakDelegate is null" );
        var stack = callerStack as StackTrace;

        if ( stack == null )
          return;

        Trace.WriteLine( stack.ToString() );
      }
      catch ( Exception )
      {
        // big error 
        // application abort
      }
    }

    public void Remove( Delegate value )
    {
      RemoveInvalidDelegates();

      foreach ( var wd in from wd in Eventhandlers.ToArray()
                          let target = wd.Target
                          where value.Target == target && value.Method == wd.Method
                          select wd )
      {
        RemoveWeakDelegate( wd );
      }
    }

    public void Add( Delegate value )
    {
      RemoveInvalidDelegates();

      Eventhandlers.Add( new WeakDelegate()
                         {
                           Target = value.Target,
                           Method = value.Method
                         } );
    }

    private void RemoveWeakDelegate( WeakDelegate wd )
    {
      if ( wd == null )
        return;

      wd.Dispose();
      Eventhandlers.Remove( wd );
    }

    public void Clear()
    {
      foreach ( var wd in Eventhandlers.ToArray() )
        RemoveWeakDelegate( wd );
    }

    protected void RemoveInvalidDelegates()
    {
      foreach ( var wd in Eventhandlers.ToArray().Where( IsWeakDelegateInvalid ) )
        RemoveWeakDelegate( wd );
    }

    protected static bool IsWeakDelegateInvalid( WeakDelegate weakDelegate )
    {
      return IsWeakDelegateInvalid( weakDelegate.Target, weakDelegate.Method );
    }

    protected static bool IsWeakDelegateInvalid( object target, MethodInfo method )
    {
      return target == null && !method.IsStatic;
    }

    public int GetInvocationCount()
    {
      return Eventhandlers.Count;
    }

    #region IDisposable Members

    public void Dispose()
    {
      Clear();
    }

    #endregion
  }
}
